package fem2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import fem2.enu.MeshPartType;
import fem2.enu.NeighbourCriteria;

/**
 * 
 * @author hbui
 * 
 */
public class PatchManager {

	private ArrayList<Patch> patches;
	private Set<Node> vertexNodes;
	private Set<Node> midSideNodes;
	private Set<Node> cornerNodes;
	private Set<Node> midSideCornerNodes;
	// private Set<Node> interiorNodes;
	private Map<Node, ArrayList<Patch>> mapNodeToPatches;

	private Map<Node, Node[]> mapMidSideNodeToVertexNodes;

	public PatchManager(Mesh mesh) {
		this();
		buildPatches(mesh);
	}

	public PatchManager() {
		vertexNodes = new HashSet<Node>();
		midSideNodes = new HashSet<Node>();
		cornerNodes = new HashSet<Node>();
		midSideCornerNodes = new HashSet<Node>();
		// interiorNodes = new HashSet<Node>();
		patches = new ArrayList<Patch>();
		mapNodeToPatches = new HashMap<Node, ArrayList<Patch>>();
		mapMidSideNodeToVertexNodes = new HashMap<Node, Node[]>();
	}

	private void build2dPatches(Mesh mesh) {
		int nf = mesh.countFaces();
		/*
		 * define node relativities
		 */
		for (int i = 0; i < nf; i++) {
			Face f = mesh.getFace(i);
			MeshPartType type = f.getType();
			Node[] nodes = f.getNodes();
			/*
			 * if face is 3 node triangle or 4 node quadrilateral then all of
			 * its nodes are vertex nodes
			 */
			if (type == MeshPartType.TRI3 || type == MeshPartType.QUAD4) {
				for (Node n : nodes) {
					vertexNodes.add(n);
				}
			}
			/*
			 * if face is 6 node triangle then the vertex nodes are n1,n2,n3,
			 * remaining are mid-side nodes
			 */
			else if (type == MeshPartType.TRI6) {
				for (int j = 0; j < 3; j++) {
					vertexNodes.add(nodes[j]);
				}
				for (int j = 3; j < 6; j++) {
					midSideNodes.add(nodes[j]);
					Node[] neighbours = null;
					if (j == 3) {
						neighbours = new Node[] { nodes[0], nodes[1] };
					} else if (j == 4) {
						neighbours = new Node[] { nodes[1], nodes[2] };
					} else if (j == 5) {
						neighbours = new Node[] { nodes[0], nodes[2] };
					}
					mapMidSideNodeToVertexNodes.put(nodes[j], neighbours);
				}
			}
			/*
			 * if face is 8 node quadrilateral then the vertex nodes are
			 * n1,n2,n3,n4 remaining are mid-side nodes
			 */
			else if (type == MeshPartType.QUAD8) {
				for (int j = 0; j < 4; j++) {
					vertexNodes.add(nodes[j]);
				}
				for (int j = 4; j < 8; j++) {
					midSideNodes.add(nodes[j]);
					Node[] neighbours = null;
					if (j == 4) {
						neighbours = new Node[] { nodes[0], nodes[1] };
					} else if (j == 5) {
						neighbours = new Node[] { nodes[1], nodes[2] };
					} else if (j == 6) {
						neighbours = new Node[] { nodes[2], nodes[3] };
					} else if (j == 7) {
						neighbours = new Node[] { nodes[3], nodes[0] };
					}
					mapMidSideNodeToVertexNodes.put(nodes[j], neighbours);
				}
			}
			/*
			 * 9-node quadrilateral...
			 */
			else if (type == MeshPartType.QUAD9) {
				/*
				 * TODO
				 */
			} else {
				throw new Error("invalid mesh part type");
			}
		}
		/*
		 * build patches for vertex node
		 */
		for (Node n : vertexNodes) {
			ArrayList<MeshPart> faces = n.getNeighbours(mesh,
					NeighbourCriteria.FACES_NEIGHBOUR_WITH_NODE);
			/*
			 * if the vertex node's neighbours are more than 2 faces then the
			 * patch contains all of its neighbour faces
			 */
			if (faces.size() > 1) {
				Patch p = new Patch(faces);
				ArrayList<Patch> pArray = new ArrayList<Patch>();
				pArray.add(p);
				mapNodeToPatches.put(n, pArray);
				patches.add(p);
			}
			/*
			 * if neighbours contain 1 face then it's a corner node
			 */
			else {
				cornerNodes.add(n);
			}
		}

		// System.out.println("vertex nodes:");
		// for (Node n : vertexNodes) {
		// n.print();
		// System.out.println();
		// }

		/*
		 * remove corner nodes from vertex nodes
		 */
		vertexNodes.removeAll(cornerNodes);

		// System.out.println("vertex nodes:");
		// for (Node n : vertexNodes) {
		// n.print();
		// System.out.println();
		// }
		// System.out.println("corner nodes:");
		// for (Node n : cornerNodes) {
		// n.print();
		// System.out.println();
		// }

		/*
		 * build patches for corner node
		 */
		for (Node n : cornerNodes) {
			ArrayList<MeshPart> faces = n.getNeighbours(mesh,
					NeighbourCriteria.FACES_NEIGHBOUR_WITH_NODE);
			Face f = (Face) faces.get(0);
			int nn = f.countModes();
			if (nn == 3) {
				Node[] nodes = f.getNodes();
				ArrayList<Patch> pArray = new ArrayList<Patch>();
				for (int i = 0; i < nn; i++) {
					if (nodes[i] != n) {
						pArray.add(mapNodeToPatches.get(nodes[i]).get(0));
					}
				}
				mapNodeToPatches.put(n, pArray);
			} else if (nn == 4) {
				Node[] nodes = f.getNodes();
				ArrayList<Patch> pArray = new ArrayList<Patch>();
				int j = 0;
				for (int i = 0; i < nn; i++) {
					if (nodes[i] == n) {
						j = i;
					}
				}
				pArray.add(mapNodeToPatches.get(nodes[(j + 2) % 4]).get(0));
				mapNodeToPatches.put(n, pArray);
			} else if (nn == 6) {
				Node[] nodes = f.getNodes();
				ArrayList<Patch> pArray = new ArrayList<Patch>();
				int j = 0;
				for (int i = 0; i < 3; i++) {
					if (nodes[i] != n) {
						pArray.add(mapNodeToPatches.get(nodes[i]).get(0));
					} else {
						j = i;
					}
				}
				mapNodeToPatches.put(n, pArray);
				Node[] neighbors = new Node[2];
				if (j == 0) {
					neighbors[0] = nodes[3];
					neighbors[1] = nodes[5];
				} else if (j == 1) {
					neighbors[0] = nodes[3];
					neighbors[1] = nodes[4];
				} else if (j == 2) {
					neighbors[0] = nodes[4];
					neighbors[1] = nodes[5];
				}
				midSideCornerNodes.add(neighbors[0]);
				midSideCornerNodes.add(neighbors[1]);
				mapNodeToPatches.put(neighbors[0], pArray);
				mapNodeToPatches.put(neighbors[1], pArray);
			} else if (nn == 8) {
				Node[] nodes = f.getNodes();
				ArrayList<Patch> pArray = new ArrayList<Patch>();
				int j = 0;
				for (int i = 0; i < 4; i++) {
					if (nodes[i] == n) {
						j = i;
					}
				}
				pArray.add(mapNodeToPatches.get(nodes[(j + 2) % 4]).get(0));
				mapNodeToPatches.put(n, pArray);
				Node[] neighbors = new Node[2];
				if (j == 0) {
					neighbors[0] = nodes[4];
					neighbors[1] = nodes[7];
				} else if (j == 1) {
					neighbors[0] = nodes[4];
					neighbors[1] = nodes[5];
				} else if (j == 2) {
					neighbors[0] = nodes[5];
					neighbors[1] = nodes[6];
				} else if (j == 3) {
					neighbors[0] = nodes[6];
					neighbors[1] = nodes[7];
				}
				midSideCornerNodes.add(neighbors[0]);
				midSideCornerNodes.add(neighbors[1]);
				mapNodeToPatches.put(neighbors[0], pArray);
				mapNodeToPatches.put(neighbors[1], pArray);
			}
		}
		/*
		 * subtract midSideNodes from midSideCornerNodes
		 */
		midSideNodes.removeAll(midSideCornerNodes);
		/*
		 * build patches for mid-side node: the patches for mid-side node was
		 * patches of its two neighbours
		 */
		for (Node n : midSideNodes) {
			Node[] neighbors = mapMidSideNodeToVertexNodes.get(n);
			ArrayList<Patch> pArray = new ArrayList<Patch>();
			pArray.add(mapNodeToPatches.get(neighbors[0]).get(0));
			pArray.add(mapNodeToPatches.get(neighbors[1]).get(0));
			mapNodeToPatches.put(n, pArray);
		}
		/*
		 * build patches for interior node
		 */
		/*
		 * TODO
		 */
	}

	private void build3dPatches(Mesh mesh) {
		/*
		 * TODO
		 */
		throw new NotImplementedError();
	}

	private void buildPatches(Mesh mesh) {
		int dim = mesh.getDimension();
		if (dim == 2) {
			build2dPatches(mesh);
		} else if (dim == 3) {
			build3dPatches(mesh);
		}
	}

	public Set<Node> getVertexNodes() {
		return vertexNodes;
	}

	public Set<Node> getMidSideNodes() {
		return midSideNodes;
	}

	public Set<Node> getCornerNodes() {
		return cornerNodes;
	}

	public Set<Node> getMidSideCornerNodes() {
		return midSideCornerNodes;
	}

	public ArrayList<Patch> getPatch(Node n) {
		return mapNodeToPatches.get(n);
	}
}
